package com.kernowsoft.wavefx;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

import com.kernowsoft.wavefx.R;

import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.SimpleCursorAdapter;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

public class WaveFxListFragment extends Fragment implements
		LoaderManager.LoaderCallbacks<Cursor>, View.OnClickListener {

	private final static String TAG = WaveFxListFragment.class.getSimpleName();

	private SimpleCursorAdapter mAdapter;
	private String mSitename = null;

	// views:
	private ListView mListView;
	private TextView mTitle, mValidTime;
	private ImageButton mOutOfDateBtn;
	private ProgressBar mRefreshProgress;

	// response receiver to handle broadcasts (specifically from the
	// forecast update service):
	private ResponseReceiver mReceiver;

	// handler for dealing with synchronising update thread with UI
	private Handler mHandler;
	private UpdateThread mUpdateThread;

	private class UpdateThread extends Thread {
		volatile boolean running = false;
	
		@Override
		public void run() {
			running = true;
			
			while (running) {
				// get main UI thread to perform update check:
				mHandler.post(new Runnable() {
					@Override
					public void run() {
						checkValidTime();
					}
				});
				
				try {
					Thread.sleep(/*1800 * */ 1000); // sleep 30 mins
				} catch (InterruptedException e) {
					Log.d(TAG, "Thread was interrupted!");
					e.printStackTrace();
				}
			}
		}
	}
	
	private void startRefreshChecker() {
		
		// keep checking forecast validity time in a loop:
		Log.d(TAG, "Started new refresh checker thread for " + mSitename);
		mUpdateThread = new UpdateThread();
		mUpdateThread.start();
	}

	@Override
	public void onActivityCreated(Bundle savedInstanceState) {
		super.onActivityCreated(savedInstanceState);
		setHasOptionsMenu(true); // this will allow fragment to put options in
									// the activities action bar
	}

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {

		View v = inflater.inflate(R.layout.layout_fx_list_view_fragment,
				container, false);

		mListView = (ListView) v.findViewById(R.id.fx_list_view);
		mListView.setAdapter(mAdapter);

		mTitle = (TextView) v.findViewById(R.id.title);
		mValidTime = (TextView) v.findViewById(R.id.validity_time);
		mOutOfDateBtn = (ImageButton) v.findViewById(R.id.out_of_date);
		mOutOfDateBtn.setOnClickListener(this);
		mRefreshProgress = (ProgressBar) v.findViewById(R.id.refresh_progress);
		mRefreshProgress.setVisibility(View.GONE);
		if (mSitename != null) {
			mTitle.setText(mSitename);
			getLoaderManager().initLoader(0, null, this);
		}

		return v;
	}

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		// create adapter
		String[] from = { WaveDataContentProvider.SiteForecast.FORECAST_PERIOD,
				WaveDataContentProvider.SiteForecast.WAVE_HSIG1,
				WaveDataContentProvider.SiteForecast.WAVE_HSIG2,
				WaveDataContentProvider.SiteForecast.WAVE_HSIG3,
				WaveDataContentProvider.SiteForecast.WAVE_TP1,
				WaveDataContentProvider.SiteForecast.WAVE_TP2,
				WaveDataContentProvider.SiteForecast.WAVE_TP3,
				WaveDataContentProvider.SiteForecast.WAVE_MDIR1,
				WaveDataContentProvider.SiteForecast.WAVE_MDIR2,
				WaveDataContentProvider.SiteForecast.WAVE_MDIR3 };

		int[] to = { R.id.wave_dt, R.id.wave_hsig1, R.id.wave_hsig2,
				R.id.wave_hsig3, R.id.wave_period1, R.id.wave_period2,
				R.id.wave_period3, R.id.wave_dir1, R.id.wave_dir2,
				R.id.wave_dir3 };

		// Adapter for list view
		mAdapter = new WaveFxCursorAdapter(getActivity(),
				R.layout.wave_fx_listview_item_2, null, from, to,
				WaveFxCursorAdapter.NO_SELECTION);

		// get sitename from arguments, if set:
		mSitename = getArguments() != null ? getArguments().getString("site")
				: null;

		// instantiate broadcast receiver:
		mReceiver = new ResponseReceiver();
		IntentFilter filter = new IntentFilter(ForecastUpdateService.FX_UPDATED);
		filter.addCategory(Intent.CATEGORY_DEFAULT);
		getActivity().registerReceiver(mReceiver, filter);

		// Reuse existing handler if you don't
		// have to override the message processing
		mHandler = new Handler();
		//mHandler = getActivity().getWindow().getDecorView().getHandler();
	}

	@Override
	public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
		// this will only get called by parent fragment if setHasOptionsMenu
		// is called in this fragments onCreate method.
		inflater.inflate(R.menu.menu_fx_list_fragment, menu);
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		// handle item selection using resource IDs from XML:
		switch (item.getItemId()) {
		case R.id.menu_fx_list_refresh:
			requestRefresh();
			return true;
		}
		return false;
	}

	public void requestRefresh() {
		// Requests the ForecastUpdateService to refresh the forecast
		// data. A broadcast intent will be fired by ForecastUpdateService
		// when the update is complete; the cursor is reloaded when then
		// broadcast is received.
		Intent i = new Intent();
		i.setClass(getActivity(), ForecastUpdateService.class);
		i.setAction(ForecastUpdateService.REQUEST_FX_UPDATE);
		i.putExtra(ForecastUpdateService.SITE_NAME, mSitename);
		getActivity().startService(i);

		// set the update button to be a refresh icon:
		mOutOfDateBtn.setVisibility(View.GONE);
		mRefreshProgress.setVisibility(View.VISIBLE);
	}

	public void setSite(String site) {
		if (mSitename == null || mSitename != site) {
			Log.d(TAG, "Setting new site: " + site);
			mSitename = site;
			mAdapter.swapCursor(null); // clear list while new data loads
			getLoaderManager().restartLoader(0, null, this);
			mTitle.setTag(mSitename);
		}
	}

	@Override
	public void onResume() {
		super.onResume();
		// start update checker:
		startRefreshChecker();
	}

	@Override
	public void onPause() {	
		// stop update thread
		Log.d(TAG, "Asking thread to stop");
		mUpdateThread.running = false;
		
		try {
			mUpdateThread.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		super.onPause();
	}
	
	@Override
	public void onDestroy() {

		// unregister the forecast update broadcast receiver, if we don't
		// then it will leak memory:
		getActivity().unregisterReceiver(mReceiver);
		
		super.onDestroy();

	}

	public void checkValidTime() {

		Log.d(TAG, "checking validity time");
		// fragment may not be properly attached to activity yet (don't know why), so dont't
		// assume getActivity will not return null...
		if(getActivity() == null) {
			Log.d(TAG, "checlValidTime: activity is null");
			return;
		}
		
		// get validity time from db:
		ContentResolver cr = getActivity().getContentResolver();
		if (cr == null) {
			Log.e(TAG, "Content Resolver was null!");
			return;
		}

		Cursor c = cr
				.query(WaveDataContentProvider.SiteForecast.VALIDTIME_URI,
						new String[] { WaveDataContentProvider.SiteForecast.VALID_DATETIME },
						WaveDataContentProvider.SiteForecast.SITENAME + " = '"
								+ mSitename + "'", null, null);

		if (c == null || !c.moveToFirst()) {
			Log.e(TAG, "Failed to return a valid time from database for site "
					+ mSitename);
			return;
		}

		Date dt = null;
		try {
			String vtime = c.getString(0);
			// Log.d(TAG, "Got valid time: " + vtime);
			dt = new SimpleDateFormat("yyyyMMddHH", Locale.getDefault())
					.parse(vtime);
			if (dt == null) {
				Log.e(TAG,
						"Failed to parse valid-date string: " + c.getString(0));
				return;
			}
			// Log.d(TAG,dt.toString() + " <--- " + vtime);
		} catch (ParseException e) {
			e.printStackTrace();
			return;
		}

		// clear the progress update indicator:
		mRefreshProgress.setVisibility(View.GONE);

		// forecast is issued at ~5 am, 5 hours after validity date, so add on 5
		// hours
		// to validity time (=29 hours):
		float diffHours = (System.currentTimeMillis() - dt.getTime()) / 1000 / 3600;
		if (diffHours > 29.0) {
			// out of date!
			mOutOfDateBtn.setVisibility(View.VISIBLE);

		} else {
			mOutOfDateBtn.setVisibility(View.GONE);
		}

		// set validity time in header:
		mValidTime
				.setText("Valid at: "
						+ new SimpleDateFormat("dd MMM yyyy HH'z'", Locale
								.getDefault()).format(dt));

	}

	@Override
	public Loader<Cursor> onCreateLoader(int arg0, Bundle bundle) {

		String[] projection = null; // all columns please.
		String query = WaveDataContentProvider.SiteForecast.SITENAME + " = '"
				+ mSitename + "'";

		return new CursorLoader(this.getActivity(),
				WaveDataContentProvider.SiteForecast.CONTENT_URI, projection,
				query, null, null);
	}

	@Override
	public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
		// check if any data returned from cursor - if not, need to update
		// content provider:
		if (cursor.getCount() == 0) {
			Log.d(TAG,
					"No data returned in cursor - calling ForecastUpdateService");
			Intent i = new Intent(getActivity(), ForecastUpdateService.class);
			i.setAction(ForecastUpdateService.REQUEST_FX_UPDATE);
			i.putExtra(ForecastUpdateService.SITE_NAME, mSitename);
			getActivity().startService(i);
			return; // Loader will be notified when DB is updated.
		}
		checkValidTime();
		mAdapter.swapCursor(cursor);
	}

	@Override
	public void onLoaderReset(Loader<Cursor> arg0) {
		mAdapter.swapCursor(null);
	}

	private void viewSpec(int fchr) {
		Intent i = new Intent(getActivity(), SpecDrawActivity.class);
		i.setAction(Intent.ACTION_VIEW);
		i.putExtra("com.kernowsoft.wavefx.SITENAME", mSitename);
		i.putExtra("com.kernowsoft.wavefx.FCHR", fchr);
		getActivity().startActivity(i);
	}

	private class WaveFxCursorAdapter extends SimpleCursorAdapter {

		public WaveFxCursorAdapter(Context context, int layout, Cursor c,
				String[] from, int[] to, int flags) {
			super(context, layout, c, from, to, flags);
		}

		@Override
		public void bindView(View v, Context context, Cursor c) {
			// super.bindView(v, context, c);
			int colIdx;
			TextView textView;

			ImageButton b = (ImageButton) v.findViewById(R.id.btn_show_spec);
			final int fchr = c
					.getInt(c
							.getColumnIndex(WaveDataContentProvider.SiteForecast.FORECAST_PERIOD));

			b.setOnClickListener(new OnClickListener() {
				@Override
				public void onClick(View v) {
					viewSpec(fchr);
				}
			});

			// set wind barb:
			WindBarb barb = (WindBarb) v.findViewById(R.id.wind_barb);
			barb.setDirection(c.getFloat(c
					.getColumnIndex(WaveDataContentProvider.SiteForecast.WIND_DIR)));
			barb.setSpeed_mps(c.getFloat(c
					.getColumnIndex(WaveDataContentProvider.SiteForecast.WIND_SPD)));

			// Set forecast text:
			textView = (TextView) v.findViewById(R.id.wave_dt);
			colIdx = c
					.getColumnIndex(WaveDataContentProvider.SiteForecast.FORECAST_PERIOD);
			textView.setText(String.format("T+%03d", c.getInt(colIdx)));

			// HSigs:
			final int[] hsig_ids = { R.id.wave_hsig1, R.id.wave_hsig2,
					R.id.wave_hsig3 };
			final String[] hsig_cols = {
					WaveDataContentProvider.SiteForecast.WAVE_HSIG1,
					WaveDataContentProvider.SiteForecast.WAVE_HSIG2,
					WaveDataContentProvider.SiteForecast.WAVE_HSIG3 };

			// Periods
			final int[] period_ids = { R.id.wave_period1, R.id.wave_period2,
					R.id.wave_period3 };
			final String[] period_cols = {
					WaveDataContentProvider.SiteForecast.WAVE_TP1,
					WaveDataContentProvider.SiteForecast.WAVE_TP2,
					WaveDataContentProvider.SiteForecast.WAVE_TP3 };

			// Directions:
			final int[] dir_ids = { R.id.wave_dir1, R.id.wave_dir2,
					R.id.wave_dir3 };
			final String[] dir_cols = {
					WaveDataContentProvider.SiteForecast.WAVE_MDIR1,
					WaveDataContentProvider.SiteForecast.WAVE_MDIR2,
					WaveDataContentProvider.SiteForecast.WAVE_MDIR3 };
			//

			for (int i = 0; i < 3; i++) {
				// HSig:
				textView = (TextView) v.findViewById(hsig_ids[i]);
				colIdx = c.getColumnIndex(hsig_cols[i]);
				float hsig = c.getFloat(colIdx);
				View parent = (View) textView.getParent();
				if (hsig == 0.0) {
					// no data - gray out this partition:
					parent.setBackgroundColor(Color.GRAY);
					textView.setText("---");
					parent.setVisibility(View.INVISIBLE);
					continue;
				} else {
					parent.setBackgroundDrawable(getResources().getDrawable(
							R.drawable.shape_grad_rect));
					parent.setVisibility(View.VISIBLE);
				}

				textView.setText(String.format("%.1fm", hsig));

				// period
				textView = (TextView) v.findViewById(period_ids[i]);
				colIdx = c.getColumnIndex(period_cols[i]);
				textView.setText(String.format("%ds",
						(int) (c.getFloat(colIdx) + 0.5)));

				// dir:
				textView = (TextView) v.findViewById(dir_ids[i]);
				colIdx = c.getColumnIndex(dir_cols[i]);
				textView.setText(String.format("%d\u00B0",
						(int) (c.getFloat(colIdx) + 0.5)));
			}

		}
	}

	private class ResponseReceiver extends BroadcastReceiver {
		@Override
		public void onReceive(Context context, Intent intent) {
			String site = intent
					.getStringExtra(ForecastUpdateService.SITE_NAME);
			Log.d(TAG, "Recieved broadcast, fx update for " + site);

			checkValidTime();
		}
	}

	@Override
	public void onClick(View v) {
		switch (v.getId()) {
		case R.id.out_of_date:
			requestRefresh();
			return;
		}
	}

	public void setOnItemClickListener(OnItemClickListener l) {
		mListView.setOnItemClickListener(l);
	}

}